package settings;

import logging.*;
import robot.*;
import settings.Action.*;
import settings.Action.Action;

import java.awt.datatransfer.*;
import java.io.*;
import java.util.*;

/**
 * Represents a user action, such as a keystroke or mouse click.
 * @author BDS
 */
public class Command implements Serializable, Transferable
{
	private static final long serialVersionUID = 1L;

	private UUID id;
	private Action action;
	private List<String> arguments;
	private String description;

	/**
	 * Class constructor.
	 */
	public Command()
	{
		id = UUID.randomUUID();
		action = Action.PAUSE;
		arguments = new ArrayList<String>();
		description = "";
	}
	
	/**
	 * Copy constructor.
	 * @param command The Command to copy.
	 */
	public Command(Command command)
	{
		id = UUID.randomUUID();
		action = command.getAction();
		arguments = new ArrayList<String>(command.arguments);
		description = command.description;
	}

	/**
	 * Returns the description.
	 * @return The description.
	 */
	@Override
	public String toString()
	{
		String toString = null;
		
		if (description != null && !description.equals(""))
		{
			toString = description;
		}
		else
		{
			toString = getDetails();
		}
		
		return toString;
	}

	/**
	 * Indicates whether the given Object is equivalent.
	 * @param command Object to compare.
	 * @return <code>true</code> if the Object is equal; <code>false</code> otherwise.
	 */
	@Override
	public boolean equals(Object command)
	{
		boolean equals = false;

		if ((command instanceof Command))
		{
			equals = id.equals(((Command) command).getID());
		}

		return equals;
	}
	
	/**
	 * Returns the hash code.
	 * @return The hash code.
	 */
	@Override
	public int hashCode()
	{
		int hashCode = 23;

	    hashCode = hashCode * 37 + id.hashCode();
	    hashCode = hashCode * 37 + action.hashCode();
	    
	    for (String argument : arguments)
	    {
	    	 hashCode = hashCode * 37 + argument.hashCode();
	    }
	    
	    hashCode = hashCode * 37 + description.hashCode();
	    
	    return hashCode;
	}
	
	/**
	 * Getter for the transferable data.
	 */
	@Override
	public Object getTransferData(DataFlavor dataFlavor) throws UnsupportedFlavorException, IOException
	{
		return this;
	}

	/**
	 * Getter for the supported transferable data flavors.
	 */
	@Override
	public DataFlavor[] getTransferDataFlavors()
	{
		return new DataFlavor[] { DataFlavor.stringFlavor };
	}

	/**
	 * Determines if the given DataFlavor is supported.
	 */
	@Override
	public boolean isDataFlavorSupported(DataFlavor dataFlavor)
	{
		return dataFlavor.equals(DataFlavor.stringFlavor);
	}
	
	/**
	 * Executes the Command using the provided ExecutionRobot.
	 * @param executionRobot The ExecutionRobot used to execute the Command.
	 * @return True if successful; false otherwise.
	 */
	public boolean execute(ExecutionRobot executionRobot)
	{
		boolean success = action.execute(executionRobot, arguments);
		
		if (!success)
		{
			Logger.logMessage("Halting execution");
			executionRobot.getRunningEnvironment().destroy();
		}

		return success;
	}
	
	/**
	 * Returns a String specific to the Action and arguments.
	 * @return A String specific to the Action and arguments.
	 */
	public String getDetails()
	{
		String toString = action.toString();
			
		switch (action)
		{
		case PAUSE:
			toString += ":" + (arguments.size() > 0 ? " " + arguments.get(0) : "");
			break;
		case KEYSTROKE:
			toString += ":";
			for (String key : arguments)
			{
				toString += " " + key;
			}
			break;
		case MOUSEMOVE:
			if (arguments.size() > 0)
			{
				if (MouseMoveTarget.fromName(arguments.get(0)) == MouseMoveTarget.COORDINATES)
				{
					toString += ":" + (arguments.size() > 2 ? " " + arguments.get(1) + ", " + arguments.get(2) : "");
				}
				else if (MouseMoveTarget.fromName(arguments.get(0)) == MouseMoveTarget.IMAGE)
				{
					toString += ":" + (arguments.size() > 1 ? " " + arguments.get(1) : "");
				}
			}
			break;
		case MOUSECLICK:
			break;
		case TEXTENTRY:
			toString += ":" + (arguments.size() > 0 ? " " + arguments.get(0) : "");
			break;
		case LOG:
			toString += ":" + (arguments.size() > 0 ? " " + arguments.get(0) : "");
			break;
		case VALIDATE:
			toString += ": ";
			if (arguments.size() > 0)
			{
				if (arguments.size() > 2)
				{
					if (ValidateTarget.fromName(arguments.get(0)) == ValidateTarget.ACTIVEPROCESS ||
						ValidateTarget.fromName(arguments.get(0)) == ValidateTarget.ACTIVEWINDOW)
					{
						toString += arguments.get(0) + " ";
						
						if (!Boolean.parseBoolean(arguments.get(2)))
						{
							toString += "is ";
						}
						else
						{
							toString += "is not ";
						}
						
						toString += arguments.get(1);
					}
					else if (arguments.get(1) != null && !arguments.get(1).isEmpty())
					{
						toString += arguments.get(1) + " ";
						
						if (!Boolean.parseBoolean(arguments.get(2)))
						{
							toString += "is ";
						}
						else
						{
							toString += "is not ";
						}
						
						if (ValidateTarget.fromName(arguments.get(0)) == ValidateTarget.IMAGE)
						{
							toString += "visible";
						}
						else if (ValidateTarget.fromName(arguments.get(0)) == ValidateTarget.WINDOW)
						{
							toString += "present";
						}
					}
				}
			}
			break;
		}
		
		return toString;
	}

	/**
	 * Getter for the ID.
	 * @return The ID.
	 */
	public UUID getID() { return id; }

	/**
	 * Getter for the Action.
	 * @return The Action.
	 */
	public Action getAction() { return action; }
	
	/**
	 * Setter for the Action.
	 * @param action The Action.
	 */
	public void setAction(Action action) { this.action = action; }

	/**
	 * Getter for the arguments.
	 * @return The arguments.
	 */
	public List<String> getArguments() { return arguments; }
	
	/**
	 * Setter for the arguments.
	 * @param arguments The arguments.
	 */
	public void setArguments(List<String> arguments) { this.arguments = arguments; }
	
	/**
	 * Getter for the description.
	 * @return The description.
	 */
	public String getDescription() { return description; }
	
	/**
	 * Setter for the description.
	 * @param action The description.
	 */
	public void setDescription(String description) { this.description = description; }
}
